﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Fast.Framework.Models;


namespace Fast.Framework.Extensions
{

    /// <summary>
    /// 表达式扩展类
    /// </summary>
    public static class ExpressionExtensions
    {
        /// <summary>
        /// 解析Sql
        /// </summary>
        /// <param name="expression">表达式</param>
        /// <param name="options">选项</param>
        /// <returns></returns>
        public static SqlInfo ResolveSql(this Expression expression, SqlExpressionOptions options)
        {
            var ex = new SqlExpressionVisitor(options);
            ex.Visit(expression);
            return ex.sqlInfo;
        }
    }

    /// <summary>
    /// 表达式映射
    /// </summary>
    public static class ExpressionMapper
    {
        /// <summary>
        /// 表达式类型
        /// </summary>
        public static readonly Dictionary<ExpressionType, string> expressionTypes;

        /// <summary>
        /// 方法调用
        /// </summary>
        public static readonly Dictionary<string, Action<SqlInfo, MethodCallExpression, Action<Expression>>> methodCall;

        /// <summary>
        /// 方法参数
        /// </summary>
        public static readonly Dictionary<string, Func<object, object>> methodArguments;

        /// <summary>
        /// 构造方法
        /// </summary>
        static ExpressionMapper()
        {
            expressionTypes = new Dictionary<ExpressionType, string>()
            {
                {ExpressionType.Add,"+" },
                {ExpressionType.Subtract,"-" },
                {ExpressionType.Multiply,"*" },
                {ExpressionType.Divide,"/" },
                {ExpressionType.AndAlso,"AND" },
                {ExpressionType.OrElse,"OR" },
                {ExpressionType.Equal,"=" },
                {ExpressionType.NotEqual,"<>" },
                {ExpressionType.GreaterThan,">" },
                {ExpressionType.LessThan,"<" },
                {ExpressionType.GreaterThanOrEqual,">=" },
                {ExpressionType.LessThanOrEqual,"<=" }
            };

            methodCall = new Dictionary<string, Action<SqlInfo, MethodCallExpression, Action<Expression>>>()
            {
                #region 聚合函数
                {"Sum", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("SUM");
                } },
                {"Max", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("MAX");
                } },
                {"Min", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("MIN");
                } },
                {"Count", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("COUNT");
                } },
                {"Avg", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("AVG");
                } },
                #endregion

                #region 日期函数
                {"GetDate", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("GETDATE");
                } },
                {"Year", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("YEAR");
                } },
                {"Month", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("MONTH");
                } },
                {"Day", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("DAY");
                } },
                {"Current_Timestamp", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("CURRENT_TIMESTAMP");
                } },
                {"Current_Date", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("CURRENT_DATE");
                } },
                {"Current_Time", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("CURRENT_TIME");
                } },
                #endregion

                #region 数学函数
                {"Abs", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("ABS");
                } },
                {"Round", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[1]);
                sql.SqlStack.Push(",");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("ROUND");
                } },
                #endregion

                #region 字符串函数
                {"Contains", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push(" LIKE ");
                visit.Invoke(node.Object);
                } },
                {"StartsWith", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push(" LIKE ");
                visit.Invoke(node.Object);
                } },
                {"EndsWith", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push(" LIKE ");
                visit.Invoke(node.Object);
                } },
                {"Substring", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                if (node.Arguments.Count > 1)
                {
                    visit.Invoke(node.Arguments[1]);
                    sql.SqlStack.Push(",");
                }
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push(",");
                visit.Invoke(node.Object);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("SUBSTRING");
                } },
                {"Replace", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[1]);
                sql.SqlStack.Push(",");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push(",");
                visit.Invoke(node.Object);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("REPLACE");
                } },
                {"ToUpper", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Object);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("UPPER");
                } },
                {"ToLower", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Object);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("LOWER");
                } },
                {"Trim", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Object);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("TRIM");
                } },
                #endregion

                #region 其它函数
                {"Equals", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push(" = ");
                visit.Invoke(node.Object);
                } },
                {"ToDateTime", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToChar", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToString", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToDecimal", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToDouble", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToInt16", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToInt32", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToInt64", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"ToBoolean", (sql,node,visit)=>
                {
                visit.Invoke(node.Arguments[0]);
                visit.Invoke(node.Object);
                } },
                {"IsNull", (sql,node,visit)=>
                {
                sql.SqlStack.Push(")");
                visit.Invoke(node.Arguments[1]);
                sql.SqlStack.Push(",");
                visit.Invoke(node.Arguments[0]);
                sql.SqlStack.Push("(");
                sql.SqlStack.Push("ISNULL");
                } }
	#endregion
            };

            methodArguments = new Dictionary<string, Func<object, object>>()
            {
                { "",value=>value},
                {"ToDateTime",value=> Convert.ToDateTime(value) },
                {"ToChar",value=> Convert.ToChar(value) },
                {"ToString",value=>Convert.ToString(value) },
                {"ToDecimal",value=>Convert.ToDecimal(value) },
                {"ToDouble",value=> Convert.ToDouble(value) },
                {"ToInt16",value=>Convert.ToInt16(value) },
                {"ToInt32",value=> Convert.ToInt32(value) },
                {"ToInt64",value=>Convert.ToInt64(value) },
                {"ToBoolean",value=>Convert.ToBoolean(value) },
                {"Contains",value=>$"%{value}%" },
                {"StartsWith",value=>$"{value}%" },
                {"EndsWith",value=>$"%{value}" }
            };
        }
    }

    /// <summary>
    /// Sql表达式访问
    /// </summary>
    public class SqlExpressionVisitor : ExpressionVisitor
    {

        /// <summary>
        /// Sql信息
        /// </summary>
        public readonly SqlInfo sqlInfo;

        /// <summary>
        /// 参数索引
        /// </summary>
        private readonly Dictionary<string, int> parameterIndex;

        /// <summary>
        /// 成员信息
        /// </summary>
        private readonly Stack<MemberInfo> memberInfos;

        /// <summary>
        /// 方法名称
        /// </summary>
        private string methodName = "";

        /// <summary>
        /// Sql表达式选项
        /// </summary>
        private readonly SqlExpressionOptions options;

        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="options">Sql表达式选项</param>
        public SqlExpressionVisitor(SqlExpressionOptions options)
        {
            sqlInfo = new SqlInfo();
            parameterIndex = new Dictionary<string, int>();
            memberInfos = new Stack<MemberInfo>();
            this.options = options;
        }

        /// <summary>
        /// 访问
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        [return: NotNullIfNotNull("node")]
        public override Expression Visit(Expression node)
        {
            //Console.WriteLine(node.NodeType);
            return base.Visit(node);
        }

        /// <summary>
        /// 访问Lambda
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="node">节点</param>
        /// <returns></returns>
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            var index = 0;
            foreach (var parameter in node.Parameters)
            {
                parameterIndex.Add(parameter.Name, index);
                index++;
            }
            return Visit(node.Body);
        }

        /// <summary>
        /// 访问进制
        /// </summary>
        /// <param name="node">节点</param>
        /// <returns></returns>
        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (!ExpressionMapper.expressionTypes.ContainsKey(node.NodeType))
            {
                throw new NotSupportedException($"表达式类型{node.NodeType}不支持转换");
            }
            var op = ExpressionMapper.expressionTypes[node.NodeType];
            var isBinaryExpression = false;
            if (node.Right.NodeType == ExpressionType.Constant && ((ConstantExpression)node.Right).Value == null)//判断null值
            {
                if (op == "=")
                {
                    sqlInfo.SqlStack.Push(" IS NULL");
                }
                else if (op == "<>")
                {
                    sqlInfo.SqlStack.Push(" IS NOT NULL");
                }
            }
            else
            {
                isBinaryExpression = node.Left is BinaryExpression;
                if (isBinaryExpression)
                {
                    sqlInfo.SqlStack.Push(")");
                    Visit(node.Right);//右边带括号
                    sqlInfo.SqlStack.Push("(");
                }
                else
                {
                    Visit(node.Right);//右边
                }
                sqlInfo.SqlStack.Push($" {op} ");//操作符
            }
            isBinaryExpression = node.Left is BinaryExpression;
            if (isBinaryExpression)
            {
                sqlInfo.SqlStack.Push(")");
                Visit(node.Left); //左边带括号
                sqlInfo.SqlStack.Push("(");
            }
            else
            {
                Visit(node.Left); //左边
            }
            return node;
        }

        /// <summary>
        /// 访问参数
        /// </summary>
        /// <param name="node">节点</param>
        /// <returns></returns>
        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (!options.IgnoreParameterExpression)
            {
                sqlInfo.SqlStack.Push($"p{parameterIndex[node.Name]}.");
            }
            return node;
        }

        /// <summary>
        /// 访问对象
        /// </summary>
        /// <param name="node">节点</param>
        /// <returns></returns>
        protected override Expression VisitNew(NewExpression node)
        {
            for (int i = 0; i < node.Arguments.Count; i++)
            {
                var name = AddIdentifier(node.Members[i].IsDefined(typeof(ColumnAttribute)) ? node.Members[i].GetCustomAttribute<ColumnAttribute>().Name : node.Members[i].Name);
                Visit(node.Arguments[i]);
                var value = string.Join("", sqlInfo.SqlStack.ToList());
                sqlInfo.NewKeyValues.Add(name, value);
                sqlInfo.NewNames.Add(name);
                sqlInfo.NewValues.Add(value);
                sqlInfo.NewAssignMapper.Add($"{name} = {value}");
                sqlInfo.NewAsMapper.Add($"{value} AS {name}");
                sqlInfo.SqlStack.Clear();
            }
            return node;
        }

        /// <summary>
        /// 访问成员初始化
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMemberInit(MemberInitExpression node)
        {
            if (node.CanReduce)
            {
                var expression = node.Reduce();
                if (expression.NodeType == ExpressionType.Block)
                {
                    var blockExpression = (BlockExpression)expression;
                    var exs = blockExpression.Expressions.Skip(1).SkipLast(1).ToList();//去除不需要的表达式
                    for (int i = 0; i < exs.Count; i++)
                    {
                        var binaryExpression = (BinaryExpression)exs[i];
                        Visit(binaryExpression.Right);//右
                        var name = AddIdentifier(node.Bindings[i].Member.IsDefined(typeof(ColumnAttribute)) ? node.Bindings[i].Member.GetCustomAttribute<ColumnAttribute>().Name : node.Bindings[i].Member.Name);
                        var value = string.Join("", sqlInfo.SqlStack.ToList());
                        sqlInfo.NewKeyValues.Add(name, value);
                        sqlInfo.NewNames.Add(name);
                        sqlInfo.NewValues.Add(value);
                        sqlInfo.NewAssignMapper.Add($"{name} = {value}");
                        sqlInfo.NewAsMapper.Add($"{value} AS {name}");
                        sqlInfo.SqlStack.Clear();
                    }
                }
            }
            return node;
        }

        /// <summary>
        /// 访问成员
        /// </summary>
        /// <param name="node">节点</param>
        /// <returns></returns>
        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression == null)
            {
                if (node.Type.Equals(typeof(DateTime)))//如果是日期类型
                {
                    if (node.Member.MemberType == MemberTypes.Field)
                    {
                        var fieldInfo = (FieldInfo)node.Member;
                        var value = fieldInfo.GetValue(this);
                        sqlInfo.SqlStack.Push(AddQuotes(value).ToString());
                    }
                    else if (node.Member.MemberType == MemberTypes.Property)
                    {
                        var propertyInfo = (PropertyInfo)node.Member;
                        var value = propertyInfo.GetValue(this);
                        sqlInfo.SqlStack.Push(AddQuotes(value).ToString());
                    }
                }
            }
            else
            {
                if (node.Expression.NodeType == ExpressionType.Parameter)
                {
                    if (node.Member.IsDefined(typeof(ColumnAttribute)))
                    {
                        sqlInfo.SqlStack.Push(AddIdentifier(node.Member.GetCustomAttribute<ColumnAttribute>().Name));
                    }
                    else
                    {
                        sqlInfo.SqlStack.Push(AddIdentifier(node.Member.Name));
                    }
                }
                else if (node.Expression.NodeType == ExpressionType.MemberAccess)
                {
                    memberInfos.Push(node.Member);
                }
                else if (node.Expression.NodeType == ExpressionType.Constant)
                {
                    memberInfos.Push(node.Member);
                }
            }
            return base.VisitMember(node);
        }

        /// <summary>
        /// 访问常量
        /// </summary>
        /// <param name="node">节点</param>
        /// <returns></returns>
        protected override Expression VisitConstant(ConstantExpression node)
        {
            var value = node.Value;
            if (node.Type.IsClass && !node.Type.Equals(typeof(string)))
            {
                foreach (var member in memberInfos)
                {
                    if (member.MemberType == MemberTypes.Field)
                    {
                        var fieldInfo = (FieldInfo)member;
                        value = fieldInfo.GetValue(value);
                    }
                    else if (member.MemberType == MemberTypes.Property)
                    {
                        var propertyInfo = (PropertyInfo)member;
                        value = propertyInfo.GetValue(value);
                    }
                    else
                    {
                        throw new Exception($"不支持获取{member.MemberType}成员类型");
                    }
                }
                var parameterName = Guid.NewGuid().ToString().Replace("-", "");
                sqlInfo.SqlStack.Push($"{options.ParameterNotation}{parameterName}");
                sqlInfo.SqlParameters.Add(parameterName, ExpressionMapper.methodArguments[methodName].Invoke(value));
            }
            else
            {
                value = AddQuotes(ExpressionMapper.methodArguments[methodName].Invoke(value));
                sqlInfo.SqlStack.Push(Convert.ToString(value));
            }
            //最后一个节点初始化
            memberInfos.Clear();
            methodName = "";
            return node;
        }

        /// <summary>
        /// 访问方法
        /// </summary>
        /// <param name="node">节点</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (!ExpressionMapper.methodCall.ContainsKey(node.Method.Name))
            {
                throw new Exception($"{node.Method.Name}方法不支持解析");
            }
            methodName = node.Method.Name;
            ExpressionMapper.methodCall[node.Method.Name].Invoke(sqlInfo, node, e => Visit(e));//调用委托
            return node;
        }

        #region 私有公共方法

        /// <summary>
        /// 添加引号
        /// </summary>
        /// <param name="value">值</param>
        /// <returns></returns>
        private static object AddQuotes(object value)
        {
            if (value == null)
            {
                return null;
            }
            var type = value.GetType();
            if (type.Equals(typeof(char)) || type.Equals(typeof(string)) || type.Equals(typeof(DateTime)))
            {
                return $"'{value}'";
            }
            else if (type.Equals(typeof(bool)))
            {
                return Convert.ToBoolean(value) ? "1 = 1" : "0 = 1";
            }
            return value;
        }

        /// <summary>
        /// 添加识别符
        /// </summary>
        /// <param name="name">名称</param>
        /// <returns></returns>
        private string AddIdentifier(string name)
        {
            if (string.IsNullOrWhiteSpace(options.Identifier))
            {
                return name;
            }
            return options.Identifier.Insert(1, name);
        }
        #endregion

    }
}
